类
一、定义类与对象
1、定义类
class classname
{
public:
//公有数据成员、成员函数;
private:
//私有数据成员、成员函数;
protected:
//保护数据成员、成员函数;
};
一定一定注意末尾要有分号作为结束类的定义标志
数据成员、成员函数可以为空。
访问权限:
可访问性:
公有成员可访问性最强,所有东西都可以访问
保护的成员可访问性一般,在自己这个类和派生类中都能访问。(除了友元)
私有成员可访问性最弱,只有自己的类可以访问。(除了友元)
2、定义对象
class classname
{
public:
//公有数据成员、成员函数;
private:
//私有数据成员、成员函数;
protected:
//保护数据成员、成员函数;
};
//上面已经定义了一个类classname,现在用这个类定义对象,如下:
classname object_name;//定义了一个名字叫object_name的类的类型为classname的对象
3、类中成员的定义
类中的变量和普通变量定义一毛一样,但是不能赋初值。
类中的函数需要在类内申明,类外定义(和一般函数申明也一样)。(除了友元函数)一般来说,函数要写在公有部分。类外的定义要注意函数的名字
class classname
{
public:
int name (int,int);//函数类内申明
//公有数据成员、成员函数;
private:
//私有数据成员、成员函数;
protected:
//保护数据成员、成员函数;
};
int classname::name (int para1,int para2)//函数类外定义
{
//......
}
特例:内联函数直接放在类中定义就行 可以不申明inline,编译器会把类中定义的直接编译为内联函数
4、this指针
this指针是一个隐藏的指针,不用定义就会自动产生,专门用于类的成员函数。当一个对象的成员函数调用这个函数,这个对象的首地址传给this指针,所以就可以在函数内使用this->member来访问这个对象的member成员。
5、成员的访问
访问对象的格式为:对象.数据成员
访问classname类的A对象中的一个数据health,就是
classname A;//定义一个对象
int a=A.health;//读取对象A的health成员赋值给a
A.health=100;//把100写入对象A的health成员地址中
再次说明:在主函数中只能访问到公有成员
二、类的成员
构造函数是这个对象一建立起来就会运行的函数。析构函数是这个对象一删除就会运行的函数。这两个函数都不能返回值。
1、构造函数
构造函数可以有参数,所以可以重载,构造函数的名字要和类名相同
class classname
{
public:
classname();//构造函数
classname(int);//构造函数重载一号
classname(int,int);//构造函数重载二号
classname(double);//构造函数重载三号
//公有数据成员、成员函数;
private:
//私有数据成员、成员函数;
protected:
//保护数据成员、成员函数;
};
和普通函数一样,构造函数也是类内申明,类外定义,但是不用说明返回类型(因为电脑知道这是构造函数,不会有返回值)
classname::classname ()//函数类外定义
{
//......
}
构造函数用途很多,比如输出内容,让编写者知道这个类在这时候建立起来了
classname::classname ()
{
cout<<"这个类现在建立了哦";
}
一旦用classname这个类来构造一个对象就会输出“这个类现在建立了哦”。
构造函数最常见的用途是给类中的元素赋初值,比如下面是某地警察的犯罪记录次数系统:
class fanzuicishu
{
public:
fanzuicishu();
fanzuicishu(string,int);
private:
string name;
int times;
};
fanzuicishu::crime ()
{
cout<<"您搁这输入空气呢?";
}
fanzuicishu::crime (string input_name,int input_times)
{
name=input_name;
times=input_times;
}
当主函数中有:
fanzuicishu zs;
zs();
就会输出:您搁这输入空气呢?
如果这样:
fanzuicishu zs;
zs(zhangsan,20);
那么zs这个对象的数据成员name就会被赋值zhangsan,times会被复制20;这样就不用新开一个函数来单独修改新开对象zs的name和times。
这样一来,张三的名字(zhangsan)和犯罪次数(20次)在zs这个对象一建立的时候就被写进了警察局的系统里面。(当然现实中肯定没这么低级)
拷贝构造函数
将构造函数重载为可以把数据成员复制到新的一个对象的类上面,比如某个游戏中一个人有金钱和生命两个数据:
class classname
{
public:
classname();//默认构造函数
classname(int money,int health);//重载构造函数为开辟一个人的金钱和生命的内存空间并初始化,定义略
classname(const classname &,int =1,int =1 );//重载为拷贝构造函数,和默认构造函数的区别就是有后两个参数,使电脑知道你在调用这个重载函数而不是默认构造函数,定义在下面
private:
int money;
int health;
protected:
};
classname::classname(const classname &A,int x=1,int y=1)//拷贝构造函数定义
{
money=A.money;//复制金钱
health=A.health;//复制生命
}
...//其他函数定义略
int main()
{
classname A(100,10);//A的人有金钱100money,有生命10health
classname B(A,0,0);//利用三个参数使电脑知道你在调用拷贝构造函数,新开对象B这个人,并且把A这个人的金钱,生命复制给B
}
2、析构函数
析构函数在类被删除的时候运行,一般用于告诉程序员,这个对象消失了,或者清除类中开的内存。和构造函数一个道理,但是不能加入参数。
析构函数也是类内申明,类外定义。格式为:同构造函数,与类名字一样,前面加上:~,如下:
class classname
{
public:
classname();//构造函数
~classname();//析构函数
private:
protected:
}
注意:析构函数不加参数,所以不能重载
3、常数据成员
就是和平时都常数一样,前面加const就好,直接在类内说明时定义值即可。
4、常对象
申明对象时,前面加上const,常对象只读
class classname
{
public:
...
private:
...
protected:
};
const classname A(1,2);//申明常对象,常对象只能调用构造函数赋初始值,一旦赋值,不能再改
5、常成员函数
常成员函数不能修改类中的数据成员,只能读
函数申明时和定义时,参数表的后面加上const,其他和普通成员函数一样
class classname
{
public:
int function1(int,int) const;//末尾加const
private:
...
protected:
};
int classname::function1(int parameter1,int parameter2) const //定义后面加const
{
...
}
6、静态成员
静态成员是所有这个类的对象所公有的
一个例子
比如十个人拥有自己的属性,那么这十个人就是属于人这个类的十个对象,每个对象有自己的属性,但是这十个人共用一张银行卡,这个银行卡里面面的钱数量受到所有人的控制。假设账户原来有1000块,第一个人把银行卡花了100块,账户里剩下900块,900由这十个人共享,不属于任何一个人。
上面那个例子中,人是类,每个人是属于类的对象,而银行卡的钱数量是静态数据成员。
类内申明静态成员时,前面加上static,类外定义不必再说明static。静态成员不属于任何一个对象,是公共的。
静态成员也是在类外定义,不初始化默认为0,初始化需要用类名初始化而不是某个对象(这也说明静态成员不属于任何一个对象)。
静态成员详情
静态成员在程序开始运行时就分配内存空间(分配空间初始化值为0),程序结束才释放空间,所以一直存在,不必在主函数开始运行后(或者定义对象之后)才赋值。
class classname
{
public:
static int money;//静态成员:钱的数量
private:
...
protected:
};
int classname::money=1;//初始化用类的名字,不是对象,静态成员不初始化默认为0,不必在主函数中赋值
int main()
{
classname object;
}
7、静态成员函数
静态成员函数只能访问类的静态数据成员,不能用于其他用途
class classname
{
public:
static int money;//静态成员:钱的数量
static int get_money(int);//静态成员函数:获取金钱数量
static int add_money(int);//静态成员函数:增加金钱数量
private:
...
protected:
};
int classname::get_money(int the_money)
{
...
}
int classname::add_money(int the_money)
{
...
}
int main()
{
...
}
静态成员函数也是类内申明static类外不必static
三、友元
1、友元的性质
类classname的友元可以直接访问classname这个类的所有数据成员,包括私有。
友元可以是普通函数,可以是成员函数,也可以是另外一个类
类classname的友元可以直接访问classname这个类的所有数据成员,包括私有。
友元不具有对称性和传递性,比如B函数是A的友元,B可以使用A的所有成员,但除非特殊说明,A不可以用B的成员。A是B的友元,B是C的友元,但A不能用C中的数据成员
2、友元的申明与定义
说明函数f是类A的友元,直接在类A的f函数前面加上friend
class A
{
public:
...
friend void f();//申明f是类A友元
private:
...
int the_thing_he_knows ;
protected:
...
};
这样f就可以直接访问类A的the_thing_he_knows
在定义的时候,不需要void A::f()因为友元不属于这个类,把f放在类A中并且加上friend只是为了说明f是类A的友元
void f()//外部定义不需要表明f在A中,也不需要说明friend,直接当有访问权限的普通函数定义。
{
cout<<the_thing_he_knows;
}
友元类同理,申明B是A的友元类,则类A中所有东西类B可以随便使用
四、类的继承与派生
1、有关继承和派生
关于类继承和派生的一个比喻
继承和派生其实是一个东西的两种说法
儿子继承了爸爸的基因,爸爸派生出儿子,差不多这个意思
类B继承类A,则类B中除了有从类A中继承过来的成员,还有自己的成员。而A没有B的成员。
不恰当的比喻:爸爸A把基因全部传给儿子B,儿子B有爸爸A全部基因,自己还额外出现一些基因。爸爸A没有儿子B新出现的基因。
另外一个比喻
类A:植物
类B:低等植物
类C:高等植物
那么B、C两个类是由A派生出来的,继承了A的属性。A有点属性,B、C都有。
植物有属性:由细胞组成,那么其派生类B、C继承了这个属性,由细胞组成
但是高等植物C有植物A没有的属性,低等植物B也有植物A没有的属性。
在这里,植物类A是基类,B、C是派生类
被继承的类(爸爸)相对于继承的类(儿子)叫做基类,继承的类(儿子)叫做派生类。
用图表示:箭头由派生类指向基类,如下图,A是基类,B、C是A的派生类
继承关系是相对的,比如下图
A是B的(直接)基类,A是C的(间接)基类,B是C的基类
2、继承的权限
继承总共有三类:
- 公有继承
- 私有继承
- 保护继承
继承规则:
| 继承类型 | 继承前(基类)可访问性 | 继承后(派生类)可访问性 |
| 公有继承 | 公有 | 公有 |
| 保护 | 保护 | |
| 私有 | 不可继承 | |
| 私有继承 | 公有 | 私有 |
| 保护 | 私有 | |
| 私有 | 不可继承 | |
| 保护继承 | 公有 | 保护 |
| 保护 | 保护 | |
| 私有 | 不可继承 |
对上表的理解
表看起来复杂,实则有规律可循
私有成员不能被继承:前文说过,类的私有数据只有在类内可以访问,在类外和派生类都不能访问,所以以下都抛开基类私有成员
公有继承下,公有还是公有,保护还是保护
保护继承下,全部变成保护
私有继承下,全部变成私有
一般程序都是用公有继承,保护继承和私有继承很少用
3、继承的写法
先构造一个植物类
class plants
{
public:
...
private:
...
protected:
...
};
然后把plants作为基类,构造高等植物和低等植物派生类
class higher_plants:public plants//*
{
public:
...
private:
...
protected:
...
};
class lower_plants:public plants//*
{
public:
...
private:
...
protected:
...
};
就是像打*号的地方这样,在类名字后面跟上继承类型public和基类的名字plants
派生类可以有多个(直接)基类,如果一个派生类有不止一个(直接)基类
class higher_plants:public plants, public type_1, private type_2
{
public:
...
private:
...
protected:
...
};
像这样,写出所有继承类型直接基类的名字并用逗号隔开